home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Ian & Stuart's Australian Mac 1993 September
/
September 93.iso
/
Archives
/
Sound
/
MIDI
/
MIDI Utilities
/
CMU Midi Toolkit
/
Source
/
phase1.c
< prev
next >
Wrap
Text File
|
1987-02-12
|
39KB
|
1,365 lines
/* phase1.c
*
* phase1 parses an adagio input stream and builds a linked list structure
* consisting of notes and control changes in time order
*/
/*****************************************************************************
* Change Log
* Date | Change
*-----------+-----------------------------------------------------------------
* 31-Dec-85 | Created changelog
* 31-Dec-85 | Added standard command scanner, metronome variable, need to add
* | cmdline_help procedure
* 1-Jan-86 | Put error messages out to stderr
* 18-Jan-86 | Shortened durations by 1/200 s to avoid roundoff problems --
* | see buildnote for details
* 3-Mar-86 | Allow octave and accidentals in either order after pitch name
* | Default octave is now one that gets nearest previous pitch,
* | the tritone (half an octave) interval is descending by default
* | Special commands handled by table search
* | !Rate command added to scale all times by a percentage
* | (50 = half speed)
* 12-Mar-86 | Broke off command line parser into adagio.c, only parser remains
* 24-Mar-86 | Changed representation from note_struct to event_struct
* | Parse M, N, O, X, and Y as control change commands
* 23-May-86 | Added , and ; syntax: "," means "N0\n", ";" means "\n"
* 16-Jul-86 | Modify to only call toupper/lower with upper/lower case as
* | parameter to be compatible with standard C functions
* 7-Aug-86 | Fixed bug with default pitches and rests
* 23-Oct-86 | JMaloney: Improved efficiency of note sorter by keeping
* | hint pointers.
* 4-Nov-86 | JMaloney: Changed memory allocator to avoid large per-node
* | overhead of malloc.
* 5-Nov-86 | JMaloney: Made scanint return long. Made durations and times
* | unsigned longs because we were running out of bits in the
* | duration calculations. Changed radix point to 1024 and
* | reversedthe order of multiplying and dividing in rate and
* | tempo calculations because of overflow danger. (We now do the
* | divides *first*.) Put in error check for durations too large
* | to be represented as a sixteen bit unsigned number.
* 10-Nov-86 | JMaloney & RJoseph: Converted to LighspeedC
* | Changed "line" to "lineBuff"
* 4-Jan-87 | JMaloney: Get input from a stream
* 11-Jan-87 | JMaloney: Fixed Rate scaling to apply to U and T commands
*****************************************************************************/
#include "switches.h"
#ifdef LIGHTSPEED
#include <StdIO.h>
#include <CType.h>
#include <MemoryMgr.h>
#endif
#ifdef MPW
#include <StdIO.h>
#include <CType.h>
#include <Types.h>
#include <Memory.h>
#endif
#include "cext.h"
#include "adagio.h"
#include "cmdline.h"
#include "stream.h"
#include "phase1.h"
#include "phase2.h"
#include "userio.h"
/****************************************************************************
*
* constants, types, and macros
*
****************************************************************************/
/* the following are used to simulate fixed point numbers with
* the radix point 12 bits from the right: */
#define precise(x) (((ulong) x) << 12)
#define round(x) ((((ulong) x) + 2048L) >> 12)
#define trunc(x) (((ulong) x) >> 12)
#define nullstring(s) ((s)[0] == '\0')
/* MAX_DUR is the maximum duration which may be represented by 16 bits
* NOTE: absolute times (vs. durations) are represented by 32 bits */
#define MAX_DUR 0xFFFFL
/* this structure is used in allocating/freeing large chunks of
* memory; see event_alloc for details
*/
typedef struct mem_chunk {
struct mem_chunk *next;
} *MemChunkPtr;
/* allocate this 2000 notes at a time */
#define CHUNK_SIZE (2000L * sizeof(struct event_struct))
/****************************************************************************
*
* routines private to this module
*
****************************************************************************/
void do_a_rest(void);
void doabsdur(void);
void doabspitch(void);
void docomment(void);
void doctrl(int);
void dodur(void);
void doerror(void);
void doloud(void);
void donextdur(void);
void dopitch(void);
void doprogram(void);
void dorate(void);
void dospecial(void);
void dotempo(void);
void dotime(void);
void dovoice(void);
event_type event_alloc(void);
void fferror(char *);
void init(void);
boolean ins_ctrl(event_type *);
void ins_event(event_type *, event_type);
boolean ins_note(event_type *);
int issymbol(void);
void marker(int);
void parseend(void);
void parsefield(void);
boolean parsenote(event_type *);
ulong parsesymdur(void);
ulong scaleby(ulong, ulong, boolean);
int scan(char *);
ulong scanint(void);
int scanterminator(char *);
/****************************************************************************
*
* parser tables
*
****************************************************************************/
/* symbol (keyword) table: */
#define sym_n 3
private char *ssymbols[sym_n] = {"RATE", "TEMPO", "ENDSCORE"};
#define sym_rate 0
#define sym_tempo 1
#define sym_endscore 2
/* loudness translation table: */
struct loudt {
char symbol[4];
int value;
};
struct loudt loudtable[] = {
"PPP", 20,
"PP\0", 26,
"P\0\0", 34,
"MP\0", 44,
"MF\0", 58,
"F\0\0", 75,
"FF\0", 98,
"FFF", 127
};
private int pitchtable[7] = {57, 59, 48, 50, 52, 53, 55};
/****************************************************************************
*
* variables private to this module
*
****************************************************************************/
/* hints to optimize score sorting: */
private event_type last_event = NULL; /* last event inserted */
private event_type ref_event = NULL; /* last_event as of the most recent
* !rate or !tempo directive */
/* storage allocation bookkeeping: */
private MemChunkPtr firstChunk = NULL; /* first in chain of allocated chunks */
private event_type eventPool = NULL; /* next event to alloc */
private ulong eventsInChunk = 0; /* events left in this chunk */
/* misc: */
private boolean debug = false; /* controls verbose printout */
private boolean endFlag = false; /* set "true" when "!ENDSCORE" is seen */
private ulong note_count = 0; /* number of notes translated */
private ulong ctrl_count = 0; /* number of control commands translated */
/* input and lexical analysis variables: */
#define LINE_SIZE 100
private char lineBuff[LINE_SIZE]; /* the input line */
private char token[LINE_SIZE]; /* a token from the input line */
private int linex; /* index of the next character to be scanned */
private int fieldx; /* index of the current character within a field */
private boolean ndurp;
/* set when the time for the next event (N) is indicated
* (next time defaults to the current time plus duration unless
* overridden by a next (N) command whose presence is signalled
* by ndurp)
* NOTE: this flag is NOT inherited by the next line */
private boolean pitch_flag;
/* set when a pitch is indicated
* (if controls changes are given, only allocate a note event if
* a pitch was specified -- i.e. when pitch_flag is set)
* NOTE: this flag is NOT inherited by the next line */
private boolean rest_flag;
/* set when a rest (R) is indicated
* NOTE: this flag is NOT inherited by the next line */
/****************************************************************************
* state variables
*
* because each line of an Adagio score inherits properties from the previous
* line, it makes sense to implement the parser as a collection of routines
* that make small changes to some global state. for example, pitch is a
* global variable. when the field G4 is encountered, the dopitch routine
* assigns the pitch number for G4 to the variable pitch. after all fields
* are processed, these variables describe the current note and contain the
* default parameters for the next note as well.
*
* global variables that are used in this way by the parsing rountines are:
****************************************************************************/
private boolean symbolic_dur_flag;
/* true if last dur was not absolute
* (if this is set, then the default duration is changed
* accordingly when the tempo is changed) */
private boolean ctrlflag[nctrl];
/* ctrlflag[i] is set to indicate a control change for the ith controller
* the new value for the controller is in ctrlval[i]
* ctrlflag[0] is true if ANY control change was present */
private ushort ctrlval[nctrl];
/* the new value of each control that changed
* NOTE: the value of ctrlval[i] is only significant
* if ctrlflag[i] is true */
private int
lineno, /* current line number */
pitch, /* pitch of note (may be negative) */
loud, /* loudness of note */
voice, /* voice (midi channel) of note */
program, /* midi program (timbre control) of note */
last_prog; /* saved value of program inherited from previous line
/* (this is needed to implement the rule that note events
* are generated for rests if the program has changed) */
private ulong
dur, /* the duration of the note */
ntime, /* the starting time of the next note (ntime after this one) */
rate, /* the current rate -- scales times and durations, unity = 100 */
tempo, /* the current tempo */
thetime, /* the current time; sets the starting time of the next note */
ref_time; /* the time of the last !tempo or !rate command */
/****************************************************************************
* do_a_rest
* Effect:
* parses a rest (R) command
****************************************************************************/
private void do_a_rest()
{
if (fieldx < strlen(token))
fferror("Nothing expected after rest");
rest_flag = true;
}
/****************************************************************************
* doabsdur
* Effect:
* parses an absolute dur (U) command
****************************************************************************/
private void doabsdur()
{
if (isdigit(token[fieldx])) {
dur = precise(scanint());
dur = scaleby(dur, rate, true); /* scale by rate */
if (fieldx < strlen(token)) {
fferror("U must be followed by digits only");
}
symbolic_dur_flag = false;
} else {
fferror("No digit after U");
}
}
/****************************************************************************
* doabspitch
* Effect:
* parses an absolute pitch (P) command
****************************************************************************/
private void doabspitch()
{
if (isdigit (token[fieldx])) {
pitch = (ushort) scanint();
pitch_flag = true;
if (fieldx < strlen(token)) {
fferror("P must be followed by digits only");
} else if (pitch < minpitch) {
fieldx = 1;
fferror("Minimum pitch of -12 will be used");
pitch = minpitch;
} else if (pitch > maxpitch) {
fieldx = 1;
fferror("Maximum pitch of 115 will be used");
pitch = maxpitch;
}
} else {
fferror("No digits after P");
}
}
/****************************************************************************
* docomment
* Effect:
* parses a comment (*) command
****************************************************************************/
private void docomment()
{
lineBuff[linex] = '\0'; /* force end of line to skip comment line */
}
/****************************************************************************
* doctrl
* Inputs:
* int n: control number
* Effect:
* parses a control (J, K, M, O, X, or Y) command
****************************************************************************/
private void doctrl(n)
int n;
{
ctrlval[n] = (ushort) scanint();
if (fieldx < strlen(token)) {
fferror("Only digits expected here");
} else {
ctrlflag[n] = true;
ctrlflag[0] = true; /* ctrlflag[0] set if any flag is set */
}
}
/****************************************************************************
* dodur
* Effect:
* parses a duration (S, I, Q, H, or W) command
****************************************************************************/
private void dodur()
{
/* symbolic: scale by rate AND tempo: */
dur = parsesymdur();
dur = scaleby(dur, ((tempo * rate) / 100L), true);
symbolic_dur_flag = true;
}
/****************************************************************************
* doerror
* Effect:
* parse an unrecognized field by reporting an error
****************************************************************************/
private void doerror()
{
fieldx = 0;
fferror("Bad field");
}
/****************************************************************************
* doloud
* Effect:
* parse a loudness (L) command
****************************************************************************/
private void doloud()
{
int i, j;
if (strlen(token) < 2) {
fieldx = 0;
fferror("L must be followed by loudness indication");
return;
}
if (isdigit(token[fieldx])) {
loud = (ushort) scanint();
if (fieldx < strlen(token)) {
fferror("Digits expected after L");
} else if (loud > 127) {
fieldx = 1;
fferror("Maximum loudness of 127 will be used");
loud = 127;
}
return;
}
if (strlen(token) > 4 ) { /* maximum is 4, e.g. "Lppp" */
fieldx = 0;
fferror("Loudness field too long");
return;
}
if (strlen(token) != 4) { /* pad short symbols with 0 */
i = strlen(token); /* e.g. "p\0" -> "p\0\0" */
token[i+1] = '\0';
}
for (i = 0; i <= 7; i++) { /* loop through possibilities */
for (j = 0; j <= 2; j++) { /* test 3 characters */
if (token[fieldx+j] != loudtable[i].symbol[j])
break;
}
if (j == 3) {
loud = loudtable[i].value;
return;
}
}
fieldx = 1;
fferror("Bad loudness indication");
}
/****************************************************************************
* donextdur
* Effect:
* parse a next (N) command
****************************************************************************/
private void donextdur()
{
if (isdigit(token[fieldx])) {
ntime = precise(scanint());
ntime = scaleby(ntime, rate, false); /* scale by rate */
if (fieldx < strlen(token))
fferror("Only digits expected here");
} else {
/* symbolic: scale by rate AND tempo: */
ntime = parsesymdur();
ntime = scaleby(ntime, ((tempo * rate) / 100L), false);
}
ndurp = true; /* flag that N was given */
}
/****************************************************************************
* dopitch
* Effect:
* parses a pitch command
****************************************************************************/
private void dopitch()
{
int p;
ushort octave;
boolean octflag = false; /* set true if octave is specified */
p = pitchtable[token[0] - 'A'];
for (;;) {
if (token[fieldx] == 'S') { /* sharp */
p++;
fieldx++;
} else if (token[fieldx] == 'F') { /* flat */
p--;
fieldx++;
} else if (token[fieldx] == 'N') { /* natural */
fieldx++;
} else if (isdigit(token[fieldx]) && !octflag) {
octave = (ushort) scanint(); /* octave */
octflag = true;
} else break; /* none of the above */
}
if (octflag) {
/* adjust p to given octave */
p = (p - 48) + (12 * octave);
} else {
/* adjust p to note nearest the default pitch */
int octdiff = (p + 126 - pitch) / 12;
p = p + 120 - (octdiff * 12);
}
if (fieldx < strlen(token)) /* any unparsed characters? */
fferror("Bad pitch indication");
if (p > maxpitch) { /* pitch in range? */
fieldx = 1;
fferror("Pitch too high");
p = maxpitch;
}
pitch = p;
pitch_flag = true;
}
/****************************************************************************
* doprogram
* Effect:
* parses a program change (Z) command
****************************************************************************/
private void doprogram()
{
if (isdigit(token[fieldx])) {
program = (ushort) scanint();
if (fieldx < strlen(token)) {
fferror("Z must be followed by digits only");
} else if (program < minprogram) {
fieldx = 1;
fferror("Minimum program of 1 will be used");
program = minprogram;
} else if (program > maxprogram) {
fieldx = 1;
fferror("Maximum program of 128 will be used");
program = maxprogram;
}
} else {
fferror("No digit after Z");
}
}
/****************************************************************************
* dorate
* Effect:
* parses a !rate command
****************************************************************************/
private void dorate()
{
linex += scan(&lineBuff[linex]);
if (nullstring(token))
fferror("rate number expected");
else {
ulong oldrate = rate; /* save current rate */
fieldx = 0;
rate = scanint();
if (fieldx < strlen(token))
fferror("Only digits expected here");
if (rate == 0) {
fieldx = 0;
fferror("Rate 100 will be used here");
rate = 100;
}
ref_time = thetime; /* remember event list hint */
ref_event = last_event;
/* adjust dur in case it is inherited by next note */
dur = (dur / rate) * oldrate;
}
}
/****************************************************************************
* dospecial
* Effect:
* parses special (those starting with "!") commands
****************************************************************************/
private void dospecial()
{
switch (issymbol()) {
case sym_rate:
dorate();
break;
case sym_tempo:
dotempo();
break;
case sym_endscore:
endFlag = true;
break;
default:
fferror("Special command expected");
}
parseend(); /* flush the rest of the line */
}
/****************************************************************************
* dotempo
* Effect:
* parses a !tempo command
****************************************************************************/
private void dotempo()
{
linex += scan(&lineBuff[linex]);
if (nullstring(token)) {
fferror("Tempo number expected");
} else {
ulong oldtempo = tempo; /* save current tempo */
fieldx = 0;
tempo = scanint();
if (fieldx < strlen(token))
fferror("Only digits expected here");
if (tempo == 0) {
fieldx = 0;
fferror("Tempo 100 will be used here");
tempo = 100;
}
ref_time = thetime;
ref_event = last_event; /* remember event list hint */
/* adjust dur in case it is inherited by next note */
if (symbolic_dur_flag) {
dur = (dur / tempo) * oldtempo;
}
}
}
/****************************************************************************
* dotime
* Effect:
* parses a time (T) command
****************************************************************************/
private void dotime()
{
if (isdigit(token[fieldx])) {
thetime = precise(scanint());
thetime = scaleby(thetime, rate, false); /* scale by rate */
if (fieldx < strlen(token))
fferror("Only digits expected here");
} else {
/* symbolic: scale by rate AND tempo: */
thetime = parsesymdur();
thetime = scaleby(thetime, ((tempo * rate) / 100L), false);
}
thetime += ref_time; /* time is relative to the reference time */
}
/****************************************************************************
* dovoice
* Effect:
* parse a voice (V) command (the voice is the MIDI channel)
****************************************************************************/
private void dovoice()
{
if (isdigit(token[fieldx])) {
voice = (ushort) scanint();
if (fieldx < strlen(token))
fferror("V must be followed by digits only");
if (voice > 16) {
fferror("number too high, using 16 instead");
voice = 16;
} else if (voice < 1) {
fferror("number too low, using 1 instead");
voice = 1;
}
} else {
fferror("No digit after V");
}
}
/****************************************************************************
* event_alloc
* Returns:
* event_type: a new event structure or
* NULL if there is not enough memory left
* Effect:
* allocates memory from the heap as needed
* Implementation:
* to reduce the per block storage overhead, we allocate memory in
* large chunks and do our own allocation
* WARNING: this implementation assumes that individual events are never freed!!
****************************************************************************/
private event_type event_alloc()
{
ulong biggestChunk, junk, allocSize;
MemChunkPtr newChunk;
if (eventsInChunk <= 0) {
biggestChunk = MaxMem(&junk);
if (biggestChunk <= (SPACE_FOR_PLAY + sizeof(struct mem_chunk))) {
gprintf(FATAL,
"Out of memory in phase1, space = %ld.", biggestChunk);
clean_exit();
}
allocSize =
(biggestChunk > (SPACE_FOR_PLAY + CHUNK_SIZE)) ?
CHUNK_SIZE :
(biggestChunk - SPACE_FOR_PLAY);
/* take CHUNK_SIZE or largest fraction thereof which
* still leaves SPACE_FOR_PLAY bytes of contiguous storage */
newChunk = (MemChunkPtr) NewPtr(allocSize);
if (newChunk == NULL) {
/* should never happen */
gprintf(FATAL,
"Implementation error (phase1.c): memory allocation.");
clean_exit();
}
/* add newChunk to chunk chain */
newChunk->next = firstChunk;
firstChunk = newChunk;
/* make newChunk into new eventPool */
eventPool = (event_type) (newChunk + 1);
eventsInChunk =
(allocSize - sizeof(struct mem_chunk)) /
sizeof(struct event_struct);
}
eventsInChunk--;
return eventPool++;
}
/****************************************************************************
* fferror
* Inputs:
* char * s: an error message string
* Effect:
* prints the line with the error
* puts a cursor (^) at the error location
* prints the error message (s)
* Implementation:
* this routine prints a carat under the character that
* was copied into token[fieldx]. for example, if fieldx = 0, the
* carat will point to the first character in the field.
****************************************************************************/
private void fferror(s)
char *s;
{
gprintf(TRANS, "%5d | %s\n", lineno, lineBuff);
marker(linex - strlen(token) + fieldx + 8);
gprintf(TRANS, "Error: %s.\n", s);
}
/****************************************************************************
* phase1_FreeMem
* Effect:
* frees storage occupied by all scores created since initialization or
* the last call to FreeMem()
****************************************************************************/
public void phase1_FreeMem()
{
MemChunkPtr nextChunk = firstChunk;
MemChunkPtr temp;
while (nextChunk != NULL) {
temp = nextChunk->next;
DisposPtr(nextChunk);
nextChunk = temp;
}
firstChunk = NULL;
eventPool = NULL;
eventsInChunk = 0;
}
/****************************************************************************
* init
* Effect:
* initializes the state variables
****************************************************************************/
private void init()
{
int i;
endFlag = false;
note_count = 0;
ctrl_count = 0;
last_event = NULL;
ref_event = NULL;
firstChunk = NULL;
eventPool = NULL;
eventsInChunk = 0;
/* initial (default) values for all state variables */
symbolic_dur_flag = true; /* default dur is symbolic */
for (i = 0; i < nctrl; i++) {
/* no initial control changes */
ctrlflag[i] = false;
ctrlval[i] = 0;
}
lineno = 0;
pitch = 48; /* middle C */
loud = 127;
voice = 1;
program = 1;
last_prog = 1;
tempo = 100;
rate = 100;
dur = precise((ulong) 60); /* default dur is quarter note */
thetime = 0;
ref_time = 0;
ntime = 0;
}
/****************************************************************************
* ins_ctrl
* Inputs:
* event_type * scorePtr: a pointer to the head of a linked list
* in which to insert the control event
* Returns:
* boolean: true on success, false if not enough memory
* Effect:
* control events corresponding to current line are inserted in *scorePtr
* Implementation:
* ctrlflag[i] is true if control i was specified in this line, so
* insert one control change for each ctrlflag[i] that is true
****************************************************************************/
private boolean ins_ctrl(scorePtr)
event_type *scorePtr;
{
int i;
event_type ctrl;
for (i = 1; i < nctrl; i++) {
if (ctrlflag[i]) {
ctrl_count++;
if ((ctrl = event_alloc()) == NULL) {
return false; /* out of memory */
} else {
ctrl->ntime = round(thetime);
ctrl->nline = lineno;
ctrl->nvoice = voice;
ctrl->ncontroller = i;
ctrl->u.ctrl.value = ctrlval[i];
ctrl->next = NULL;
ins_event(scorePtr, ctrl);
}
ctrlflag[i] = false;
ctrlval[i] = 0;
}
}
return true; /* success! */
}
/****************************************************************************
* ins_event
* Inputs:
* event_type * listHeadPtr: a pointer to the head of
* the linked list in which to insert the event
* event_type event: the event to insert
* Effect:
* inserts event into the event list
* NOTE: it is inserted *after* previously inserted events with the same time
* Implementation:
* adagio files often contain many independent voices. although each voice
* consists of events in sequence, the voices are not inter-twined in the
* adagio file. rather, all the events of voice 1 appear followed by all
* the events of voice 2, and so forth. as phase one merges these event
* sequences, it must make many passes over an increasingly long list of
* events: expensive if we always start from the beginning of the list!
* we can exploit the fact that each voice is sequential by starting the
* search for the proper point of insertion at the last event inserted.
* the variable "last_event" is used to remember this hint. we
* also snapshot "last_event" in "ref_event" when a !tempo or !rate
* command occurs as another hint.
****************************************************************************/
private void ins_event(listHeadPtr, event)
event_type *listHeadPtr;
register event_type event;
{
if ((*listHeadPtr == NULL) || (event->ntime < (*listHeadPtr)->ntime)) {
/* insert at the head of the list */
event->next = *listHeadPtr;
*listHeadPtr = event;
ref_event = last_event = event;
} else {
/* insert somewhere after the head of the list
\ * assume: last_event and ref_event are not NULL. why? because they
* are only null before the first insertion, which is taken
* care of in the previous arm of this conditional. */
register event_type previous;
register event_type insert_before;
if (event->ntime >= last_event->ntime) {
/* insertion point is after last_event */
previous = last_event;
insert_before = last_event->next;
} else if (event->ntime >= ref_event->ntime) {
/* insertion point is after ref_event */
previous = ref_event;
insert_before = ref_event->next;
} else {
/* insertion point is before last_event; start at beginning */
/* assume: not inserting at very head of list; that would
* have been taken care of above */
previous = *listHeadPtr;
insert_before = previous->next;
}
while ((insert_before != NULL) &&
(event->ntime >= insert_before->ntime)) {
previous = insert_before;
insert_before = insert_before->next;
}
previous->next = event;
event->next = insert_before;
last_event = event;
}
}
/****************************************************************************
* ins_note
* Inputs:
* event_type * score: a pointer to the head of the linked list
* in which to insert the note
* Returns:
* boolean: true on success, false if not enough memory
* Effect:
* note events (if any) corresponding to the current line are inserted in score
* Implementation:
* if a note on should occur after a note off and doesn't, and the
* two notes have the same pitch, then the note off can cancel the
* note on. to make it unlikely that roundoff will cause this situation,
* dur is decreased by one half of a clock tick before rounding.
* also, phase2 gives precedence to note-offs that are simultaneous
* with note-ons.
****************************************************************************/
private boolean ins_note(score)
event_type *score;
{
event_type note;
note_count++;
if ((note = event_alloc()) == NULL) {
return false; /* out of memory */
} else {
note->ntime = round(thetime);
note->nline = lineno;
note->nvoice = voice;
note->ncontroller = 0; /* this is a note */
note->u.note.ndur = (ushort) trunc(dur);
if (rest_flag) {
note->u.note.npitch = NO_PITCH; /* a rest */
} else {
note->u.note.npitch = pitch;
}
note->u.note.nloud = loud;
note->u.note.nprogram = program;
if (debug) {
gprintf(TRANS,
"note: time %ld, dur %d, pitch %d, voice %d, loudness %d\n",
note->ntime, note->u.note.ndur, note->u.note.npitch,
note->nvoice, note->u.note.nloud);
}
note->next = NULL;
ins_event(score, note);
}
return true; /* success! */
}
/****************************************************************************
* issymbol
* Returns:
* int: symbol number, or -1 if no match
* Assumes:
* token[1] has the symbol to look up (token[0] == '!')
****************************************************************************/
private int issymbol()
{
register int symb_num;
register char *symPtr;
register char *tokenPtr;
for (symb_num = 0; symb_num < sym_n; symb_num++) {
tokenPtr = &token[1];
symPtr = ssymbols[symb_num];
for (;;) {
if ((*tokenPtr == '\0') && (*symPtr == '\0')) return symb_num;
if (*tokenPtr++ != *symPtr++) break;
}
}
return -1;
}
/****************************************************************************
* marker
* Inputs:
* int count: the number of characters to indent
* Effect:
* prints a carat (^) at the position specified on file stderr
****************************************************************************/
private void marker(count)
int count;
{
int i;
char temp[LINE_SIZE];
for (i = 0; i < count; i++) {
temp[i] = ' ';
}
temp[i] = '\0';
gprintf(TRANS, "%s^\n", temp);
}
/*****************************************************************
* parseend
* Effect:
* parse the note terminator, either ",", ";", "\0", or "\n"
****************************************************************/
private void parseend()
{
linex += scanterminator(&lineBuff[linex]);
switch (token[0]) {
case ',':
ndurp = true; /* flag that next time was specified */
ntime = 0;
break;
case ';':
case '\0':
case '\n':
break;
default:
fferror("Internal error: illegal separator?");
break;
}
}
/****************************************************************************
* parsefield
* Effect:
* looks at first character of token and calls a parsing routine
****************************************************************************/
private void parsefield()
{
fieldx = 1;
switch (token[0]) {
case 'T': dotime(); break;
case 'W':
case 'H':
case 'Q':
case 'I':
case 'S': fieldx = 0; dodur(); break;
case 'R': do_a_rest(); break;
case 'A':
case 'B':
case 'C':
case 'D':
case 'E':
case 'F':
case 'G': dopitch(); break;
case 'P': doabspitch (); break;
case 'U': doabsdur(); break;
case 'L': doloud(); break;
case 'N': donextdur(); break;
case 'J': doctrl(1); break;
case 'K': doctrl(2); break;
case 'M': doctrl(3); break;
case 'O': doctrl(4); break;
case 'X': doctrl(5); break;
case 'Y': doctrl(6); break;
case 'V': dovoice(); break;
case 'Z': doprogram(); break;
default : doerror(); break;
}
}
/****************************************************************************
* parsenote
* Inputs:
* event_type * scorePtr: pointer to base of the note list
* Effect:
* parses a note line and inserts note and/or control events (if any)
* into *scorePtr
* Assumes:
* line contains a string to be parsed
****************************************************************************/
private boolean parsenote(scorePtr)
event_type *scorePtr;
{
boolean out_of_memory = false;
/* reset the per-note flags */
ndurp = false;
pitch_flag = false;
rest_flag = false;
ctrlflag[0] = false;
/* this loop reads tokens for a note */
while (!nullstring(token)) {
parsefield();
linex += scan(&lineBuff[linex]);
}
parseend(); /* take care of note terminator */
/* insert ctrl's first so that they precede the note they effect */
if (ctrlflag[0]) out_of_memory |= !ins_ctrl(scorePtr);
/* don't reset ctrlflag[0] here; it is examined below */
/* insert a note if
* (1) a pitch was specified OR
* (2) no control was specified and this is not a rest
* (it's a pitch by default) OR
* (3) there is a program change (even if this is a rest)
*
* NOTE: program changes during rests are advised since
* synthesizers may not be able to process a program
* change followed immediately by a note-on. in fact, this
* is why we insert notes whose pitch is NO_PITCH -- so that
* the program change can be processed during the rest.
*/
if (pitch_flag ||
(!ctrlflag[0] && !rest_flag) ||
(program != last_prog)) {
out_of_memory = !ins_note(scorePtr);
last_prog = program;
}
if (ndurp) thetime += ntime;
else thetime += dur;
return out_of_memory;
}
/*****************************************************************
* parsesymdur
* Effect:
* parse and return an unscaled symbolic duration
****************************************************************/
private ulong parsesymdur()
{
register ulong symdur;
register ulong dotfactor;
switch (token[fieldx]) {
case 'W': symdur = precise(240L); break;
case 'H': symdur = precise(120L); break;
case 'Q': symdur = precise(60L); break;
case 'I': symdur = precise(30L); break;
case 'S': symdur = precise(15L); break;
default:
fferror("Duration expected: one of W, H, Q, I, or S");
return;
}
fieldx++;
dotfactor = 1;
for (;;) {
register byte c = token[fieldx];
if (c == '.') { /* dotted notation */
dotfactor = dotfactor * 2;
fieldx++;
} else if (c == 'T') { /* triplet notation */
symdur = (symdur * 2) / 3;
fieldx++;
} else if (isdigit(c)) { /* numbers are multipliers */
symdur *= scanint();
} else {
break; /* exit this loop */
}
}
if (fieldx < strlen(token)) { /* any unparsed characters? */
fferror("Bad duration");
fieldx = strlen(token) + 1;
}
return (symdur * 2) - (symdur / dotfactor);
}
/****************************************************************************
* phase1_Parse
* Inputs:
* StreamPtr inStream: input stream
* Returns:
* event_type: the parsed score
* ulong *notes: set to number of notes parsed
* ulong *controls: set to number of control events parsed
* Effect:
* parses score from inStream and builds a score data structure
****************************************************************************/
public event_type phase1_Parse(inStream, notes, controls)
StreamPtr inStream;
ulong *notes;
ulong *controls;
{
event_type score = NULL;
boolean out_of_memory = false; /* set when out of memory */
#ifndef APPLICATION
debug = cl_switch("-print");
#endif
init();
/* this loop reads lines */
while (stream_GetLine(inStream, lineBuff, LINE_SIZE) &&
(!out_of_memory) && (!endFlag)) {
lineno++;
linex = 0;
/* this loop reads notes from a line */
while ((lineBuff[linex] != '\0') &&
(!out_of_memory) && (!endFlag)) {
/* loop invariant: lineBuff[linex] is first char of next note */
linex += scan(&lineBuff[linex]);
if (!nullstring(token)) {
if (token[0] == '*') {
docomment();
} else if (token[0] == '!') {
dospecial();
} else {
out_of_memory = parsenote(&score);
}
} else {
parseend();
}
}
}
if (out_of_memory) {
gprintf(ERROR, "Out of note memory at line %ld;\n", lineno - 1);
gprintf(ERROR, " the rest of your file will be ignored.\n");
}
*notes = note_count;
*controls = ctrl_count;
return score;
}
/****************************************************************************
* scaleby
* Inputs:
* ulong raw: duration or time to scale
* ulong factor: scale factor (tempo or rate)
* boolean isDur: true to check duration bounds
* Returns:
* ulong: the raw duration or time scaled by the scale factor
* Effect:
* the duration or time is scaled by factor
* if isDur is true then the result is checked to see if it will
* fit into a 16-bit word
* Implementation:
* factor is taken to be a rate where 100 is unity (i.e. no scaling),
* 50 is half-speed, 200 is double speed, etc
* the division is done first to avoid overflow, which is especially
* a possibility when scaling large time values
****************************************************************************/
private ulong scaleby(raw, factor, isDur)
ulong raw;
ulong factor;
boolean isDur;
{
register ulong scaled = (raw / factor) * 100L;
if (isDur && (scaled > precise(MAX_DUR))) {
fferror("Duration too large to represent with 16 bits");
scaled = precise(MAX_DUR);
}
return scaled;
}
/****************************************************************************
* scan
* Inputs:
* char * start: the string to scan
* Returns:
* int: the number of characters consumed
* Effect:
* skips over leading blanks and tabs
* copies characters from start into token, converting to upper case
* scanning stops on a delimiter, which is one of:
* {space, tab, newline, null, comma, semicolon}
* Assumes:
* there will be enough room in 'token' to hold the token read
****************************************************************************/
private int scan(start)
char *start;
{
register char *src = start;
register char *dest = token;
register char c;
while ((*src == ' ') || (*src == '\t'))
src++; /* skip spaces and tabs */
while (((c = *src) != ' ') &&
(c != '\0') &&
(c != '\t') &&
(c != '\n') &&
(c != ',') &&
(c != ';')) {
*dest++ = toupper(*src++);
}
*dest = '\0';
return (src - start);
}
/****************************************************************************
* scanterminator
* Inputs:
* char * start: the string to scan
* Returns:
* int: the number of characters consumed
* Effect:
* copies a terminator character from start into token
* skips white space before terminator
****************************************************************************/
private int scanterminator(start)
register char *start;
{
register char *src = start;
register char *dst = &token[0];
while ((*src == ' ') || (*src == '\t'))
src++; /* skip spaces and tabs */
if ((*dst++ = toupper(*src)) != '\0') {
*dst = '\0';
src++;
}
return (src - start);
}
/****************************************************************************
* scanint
* Returns:
* ulong: the scanned integer
* Effect:
* scans an unsigned integer from token, starting at fieldx
* fieldx is incremented to end of the integer
****************************************************************************/
private ulong scanint()
{
register int tokenLength = strlen(token);
register char c;
register ulong num = 0;
while (fieldx < tokenLength) {
c = token[fieldx];
if ((c >= '0') && (c <= '9')) {
num = (num * 10) + (c - '0');
fieldx++;
} else {
break;
}
}
return num;
}